/***********************************************************************************************************************
*                                                                                                                      *
* SPLASH build system v0.1                                                                                             *
*                                                                                                                      *
* Copyright (c) 2013 Andrew D. Zonenberg                                                                               *
* All rights reserved.                                                                                                 *
*                                                                                                                      *
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the     *
* following conditions are met:                                                                                        *
*                                                                                                                      *
*    * Redistributions of source code must retain the above copyright notice, this list of conditions, and the         *
*      following disclaimer.                                                                                           *
*                                                                                                                      *
*    * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the       *
*      following disclaimer in the documentation and/or other materials provided with the distribution.                *
*                                                                                                                      *
*    * Neither the name of the author nor the names of any contributors may be used to endorse or promote products     *
*      derived from this software without specific prior written permission.                                           *
*                                                                                                                      *
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED   *
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL *
* THE AUTHORS BE HELD LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES        *
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR       *
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT *
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE       *
* POSSIBILITY OF SUCH DAMAGE.                                                                                          *
*                                                                                                                      *
***********************************************************************************************************************/

/**
	@brief Program entry point
 */
#include "splash.h"
#include <sys/ioctl.h>

using namespace std;

void PrintVersion();
void PrintHelp();

void InitializeBuildTree(const char* srcpath);
void Build();
void BuildTarget(string target);

double GetTime();

void ListTargets();
void ListTests();
void PrintTestOutput(string name);
void RerunTest(string name);
void Update();
void RebuildProfiling(BuildGraph* graph);

void PrepareForBuild(BuildGraph*& graph, Cluster*& cluster, vector<void*>& buildlibs, bool update_scripts = true);
void PostBuildCleanup(BuildGraph*& graph, vector<void*>& buildlibs);

size_t g_screenwidth;
size_t g_screenheight;

/**
	@brief Program entry point
 */
int main(int argc, char* argv[])
{	
	g_debugVerbosity = 0;
	
	struct winsize ws = { 0, 0, 0, 0 };
	if(ioctl(0, TIOCGWINSZ, &ws) == -1)
	{
		fprintf(stderr, "ioctl(TIOCGWINSZ) failed\n");
		return -1;
	}
	g_screenwidth = ws.ws_col;
	g_screenheight = ws.ws_row;
	
	//Parse arguments
	for(int i=1; i<argc; i++)
	{
		string sarg(argv[i]);
		
		//Print help and exit
		if( (sarg == "--help") || (sarg == "help") )
		{
			PrintHelp();
			return 0;
		}
		
		//Run a build operation
		else if(sarg == "build")
		{
			//TODO: support less than full-tree builds
			Build();
			return 0;
		}
		
		//Initialize a new build tree
		else if(sarg == "init" )
		{
			if( (i+1) >= argc)
				FatalError("\"splash init\" requires an argument\n");
			InitializeBuildTree(argv[++i]);			
			return 0;
		}
		
		//Increase verbosity level
		else if( (sarg == "--verbose") || (sarg == "-v") )
			g_debugVerbosity ++;
		
		//Print version and exit
		else if( (sarg == "--version") || (sarg == "version") )
		{
			PrintVersion();
			return 0;
		}
		
		//List targets and exit
		else if( (sarg == "--list-targets") || (sarg == "list-targets") )
		{
			ListTargets();
			return 0;
		}
		
		//List tests and exit
		else if( (sarg == "--list-tests") || (sarg == "list-tests") )
		{
			ListTests();
			return 0;
		}
		
		//Print test output
		else if( (sarg == "--print-test-output") || (sarg == "print-test-output") )
		{
			if( (i+1) >= argc)
				FatalError("\"splash print-test-output\" requires an argument\n");
			PrintTestOutput(argv[++i]);
			return 0;
		}
		
		//Re-run test
		else if( (sarg == "--rerun-test") || (sarg == "rerun-test") )
		{
			if( (i+1) >= argc)
				FatalError("\"splash rerun-test\" requires an argument\n");
			RerunTest(argv[++i]);
			return 0;
		}
		
		//Update
		else if( (sarg == "--update") || (sarg == "update") )
		{
			Update();
			return 0;
		}
		
		//Build a single target
		else if( (sarg == "--build-target") || (sarg == "build-target") )
		{
			if( (i+1) >= argc)
				FatalError("\"splash build-target\" requires an argument\n");
			BuildTarget(argv[++i]);
			return 0;
		}
		
		//Bad argument
		else
		{
			PrintHelp();
			return 1;
		}
	}
	
	//No arguments, give up
	PrintHelp();
	return 1;
}

/** 
	@brief Prints program version information
 */
void PrintVersion()
{
	printf(
		"Splash build system v0.1 by Andrew D. Zonenberg.\n"
		"\n"
		"License: 3-clause (\"new\" or \"modified\") BSD.\n"
		"This is free software: you are free to change and redistribute it.\n"
		"There is NO WARRANTY, to the extent permitted by law.\n"
		"\n");
}

/**
	@brief Print help message
 */
void PrintHelp()
{
	printf(
		"Usage: splash [global options] [mode]\n"
		"\n"
		"GLOBAL OPTIONS (must come before mode)\n"
		"    -v / --verbose      Increases debug verbosity level (may be used more than once to increase verbosity level\n"
		"\n"
		"MODES:\n"
		"    build                     Performs a build-and-test cycle on the tree in the current directory.\n"
		"    build-target              Builds a single target and does not run tests.\n"
		"    help                      Print this help message\n"
		"    init [source path]        Initializes a new build tree in the current directory from the supplied source files.\n"
		"    list-targets              Prints a list of all targets\n"
		"    list-tests                Prints a list of all tests\n"	
		"    print-test-output [test]  Prints the output of the test called \"test\"\n"
		"    rerun-test [test]         Runs a single test immediately, disregarding any cached output\n"
		"    update                    Updates the build binaries as needed\n"
		"    version                   Print program version\n"
		);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Build tree initialization

/**
	@brief Initializes a new build tree
 */
void InitializeBuildTree(const char* srcpath)
{
	//Get the relevant directories
	string buildroot = CanonicalizePath(".");
	string srcroot = CanonicalizePath(srcpath);
	DebugPrintfHeading(1, "Initializing new build tree...\n");
	DebugPrintfSilent (1, 
		"    build directory %s\n"
		"    source directory %s\n",
		buildroot.c_str(),
		srcroot.c_str());
		
	//Make sure there's a Splashfile in the root directory
	if(!DoesFileExist(srcroot + "/Splashfile"))
		FatalError("The source directory does not contain a Splashfile.\n");
		
	//Make sure this is indeed a fresh build tree, then make the metadata directory
	string metadir = buildroot + "/splash-meta";
	if(DoesDirectoryExist(metadir))
		FatalError("A new build tree cannot be created here because one already exists.\n");
	if(0 != mkdir(metadir.c_str(), 0755))
		FatalError("Failed to create build directory\n");
	
	//Make a symlink to the source directory so we can find source files in the future
	symlink(srcroot.c_str(), string(metadir + "/srcfiles").c_str());
	
	//Make directory for generated headers
	string include_dir = buildroot + "/generic/include";
	MakeDirectoryRecursive(include_dir, 0755);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Run the actual build process

void PrepareForBuild(BuildGraph*& graph, Cluster*& cluster, vector<void*>& buildlibs, bool update_scripts)
{
	double tstart = GetTime();
	
	//Assume we're building in the current directory (TODO: allow this to be changed by an argument)
	string buildroot = CanonicalizePath(".");
	DebugPrintfHeading(1, "Preparing for build...\n");
	DebugPrintfSilent (2,  "    build directory %s\n", buildroot.c_str());
		
	//Sanity check that it's a valid build directory
	string metadir = buildroot + "/splash-meta";
	if(!DoesDirectoryExist(metadir))
		FatalError("No splash-meta directory found - did you forget to run \"splash init\"?\n");
		
	//Find the source directory
	string srclink = metadir + "/srcfiles";
	if(!DoesFileExist(srclink))
		FatalError("No srcfiles symlink found.\n");
	string srcroot = CanonicalizePath(srclink);
	DebugPrintfHeading(3, "Resolved source files symlink\n");
	DebugPrintfSilent (3,  "    source directory %s\n", srcroot.c_str());
	
	graph = new BuildGraph(metadir, srcroot, buildroot);
		
	//Get the default C++ toolchain for building the build binaries
	CppToolchain* toolchain = CppToolchain::CreateDefaultToolchainCached();
	if(toolchain == NULL)
		FatalError("No C++ toolchain was found.\n");
	
	//Get the cluster configuration
	cluster = Cluster::CreateDefaultCluster();
	if(cluster == NULL)
		FatalError("No cluster was found.\n");
	
	//Default toolchain flags
	//TODO: add proper path rather than hard coding
	CppCompileFlagList cflags;
	cflags.push_back(new CppVisibilityCodeFlag(false));
	cflags.push_back(new CppDebugInfoFlag);
	cflags.push_back(new CppStandardFlag(CppStandardFlag::CPP_STANDARD_11));
	CppLinkFlagList lflags;
	lflags.push_back(new CppLinkLibraryByPathFlag("/nfs/home/azonenberg/Documents/local/programming/splash-build-system/trunk/build/bin/libsplashcore.so"));
	
	double dt = GetTime() - tstart;
	DebugPrintfHeading(1, "Setup complete in %.2f sec\n", dt);
	tstart = GetTime();
		
	//Generate the build graph
	GenerateBuildBinaries(graph, srcroot, toolchain, cflags, lflags);
	dt = GetTime() - tstart;
	DebugPrintfHeading(1, "Build binary graph created in %.2f sec\n", dt);
	tstart = GetTime();
	
	//Generate the build binaries
	if(update_scripts)
	{
		//Generate the hashes for the system includes and build binaries
		for(size_t i=0; i<graph->size(); i++)
			graph->GetNode(i)->GetHashCached();
		dt = GetTime() - tstart;
		DebugPrintfSilent(1, "    Build binary dependency scanning and hashing took %.2f sec\n", dt);
		tstart = GetTime();		
		
		//Generate the binaries themselves
		for(size_t i=0; i<graph->size(); i++)
			graph->GetNode(i)->UpdateCached(cluster);
				
		bool ok = WaitForBuildJobsToFinish(*graph);
		PrintBuildOutput(*graph);
		if(ok)
		{
			dt = GetTime() - tstart;
			DebugPrintfSilent(1, "    Build binaries created in %.2f sec\n", dt);
			tstart = GetTime();
		}
		else
			FatalError("Build binary creation failed\n");
	}
	
	//Enumerate build binaries
	vector<string> binpaths;
	for(size_t i=0; i<graph->size(); i++)
	{
		CppSharedLibraryNode* node = dynamic_cast<CppSharedLibraryNode*>(graph->GetNode(i));
		if(!node)
			continue;
		binpaths.push_back(node->GetOutputPath());
	}	
	
	dt = GetTime() - tstart;
	DebugPrintfSilent(1, "    Enumeration complete in %.2f sec\n", dt);
	tstart = GetTime();
	
	//Load them
	for(size_t i=0; i<binpaths.size(); i++)
	{
		string path = binpaths[i];
		DebugPrintfSilent(4, "    Loading build binary: %s\n", path.c_str());
		
		//Load the library
		void* plib = dlopen(path.c_str(), RTLD_LAZY | RTLD_LOCAL);
		if(plib == NULL)
			FatalError("Failed to load build binary %s\n", path.c_str());
		buildlibs.push_back(plib);
	}
	
	dt = GetTime() - tstart;
	DebugPrintfSilent(1, "    Libraries loaded in %.2f sec\n", dt);
	tstart = GetTime();
	
	//Create nodes
	for(size_t i=0; i<buildlibs.size(); i++)
	{
		string path = binpaths[i];
		void* plib = buildlibs[i];
		
		//Add nodes to the graph
		UpdateBuildGraphProc UpdateProc = reinterpret_cast<UpdateBuildGraphProc>(dlsym(plib, "CreateNodes"));
		if(UpdateProc == NULL)
			FatalError("No symbol \"CreateNodes\" found in library %s\n", path.c_str());
		UpdateProc(graph);
	}
	
	//Make toolchain output directories
	for(auto it = CppToolchain::begin(); it != CppToolchain::end(); it++)
		MakeDirectoryRecursive(buildroot + "/" + it->second->GetArchitecture() + "/include", 0755);
	
	//Create edges
	for(size_t i=0; i<buildlibs.size(); i++)
	{
		string path = binpaths[i];
		void* plib = buildlibs[i];
		
		//Add nodes to the graph
		UpdateBuildGraphProc UpdateProc = reinterpret_cast<UpdateBuildGraphProc>(dlsym(plib, "CreateEdges"));
		if(UpdateProc == NULL)
			FatalError("No symbol \"CreateEdges\" found in library %s\n", path.c_str());
		UpdateProc(graph);
	}
	
	dt = GetTime() - tstart;
	DebugPrintfSilent(1, "    Graph created in %.2f sec\n", dt);
	tstart = GetTime();
	
	//Compute hash of all nodes in deterministic order and cache it
	for(size_t i=0; i<graph->size(); i++)
		graph->GetNode(i)->GetHashCached();
		
	dt = GetTime() - tstart;
	DebugPrintfSilent(1, "    Dependency scanning and hashing took %.2f sec\n", dt);
	tstart = GetTime();
		
	//Write module info to file
	graph->WriteModuleInfo(buildroot);
	
	dt = GetTime() - tstart;
	DebugPrintfSilent(1, "    Protocol database written in %.2f sec\n", dt);
}

void Build()
{	
	BuildGraph* graph = NULL;
	Cluster* cluster = NULL;
	vector<void*> buildlibs;
	PrepareForBuild(graph, cluster, buildlibs);
	
	DebugPrintfHeading(1, "Building targets... \n");
	double tstart = GetTime();
	
	//Build all targets
	for(BuildGraph::BuildMapType::iterator it=graph->targetbegin(); it!=graph->targetend(); it++)
		it->second->UpdateCached(cluster);
	bool ok = WaitForBuildJobsToFinish(*graph);
	double dt = GetTime() - tstart;
	PrintBuildOutput(*graph);
	if(ok)
		DebugPrintfHeading(1, "Build complete (in %.2f sec)\n", dt);
	else
		FatalError("Build failed (after %.2f sec)\n", dt);
	
	//Run all tests
	DebugPrintfHeading(1, "Running tests...\n");
	tstart = GetTime();
	for(size_t i=0; i<graph->testsize(); i++)
		graph->GetTest(i)->UpdateCached(cluster);
	ok = WaitForBuildJobsToFinish(*graph);
	dt = GetTime() - tstart;
	DebugPrintfHeading(1, "Testing complete (in %.2f sec)\n", dt);
	
	//Print tests in detail including summary etc
	int fails = 0;
	for(size_t i=0; i<graph->testsize(); i++)
	{
		BuildGraphNode* node = graph->GetTest(i);
		UnitTestNode* test = dynamic_cast<UnitTestNode*>(node);
		if(test == NULL)
			continue;
		
		//Print the test
		bool pass = test->Passed();
		if( (g_debugVerbosity > 1) || !pass)
		{
			printf("%3zu/%3zu %50s ...................................................   %s\n",
				i+1, graph->testsize(), test->GetName().c_str(),
				(pass ? "PASS" : "FAIL"));
		}
		
		//Print output on failure
		if(!pass)
		{
			/*
			vector<string> output;
			if(test->GetOutput(output))
			{
				for(size_t j=0; j<output.size(); j++)
					printf("%s\n", output[j].c_str());
			}
			*/
			fails ++;
		}
	}
	int passes = graph->testsize() - fails;
	printf("%d / %d tests passed (%.2f %%)\n",
		passes, (int)graph->testsize(), static_cast<float>(100.0f * passes) / graph->testsize());

	//Done, clean up
	RebuildProfiling(graph);
	PostBuildCleanup(graph, buildlibs);
}

void PostBuildCleanup(BuildGraph*& graph, vector<void*>& buildlibs)
{
	graph->Clear();
	for(size_t i=0; i<buildlibs.size(); i++)
		dlclose(buildlibs[i]);
	buildlibs.clear();
	delete graph;
	graph = NULL;
}

void ListTargets()
{
	//Setup
	BuildGraph* graph = NULL;
	Cluster* cluster = NULL;
	vector<void*> buildlibs;
	PrepareForBuild(graph, cluster, buildlibs, false);
	
	//Walk the graph and display the list of targets
	printf("Available targets:\n");
	vector<string> targets;
	for(BuildGraph::BuildMapType::iterator it=graph->targetbegin(); it != graph->targetend(); it++)
		targets.push_back(it->first);
	sort(targets.begin(), targets.end());
	for(size_t i=0; i<targets.size(); i++)
		printf(" * %s\n", targets[i].c_str());
	
	PostBuildCleanup(graph, buildlibs);
}

void ListTests()
{
	//Setup
	BuildGraph* graph = NULL;
	Cluster* cluster = NULL;
	vector<void*> buildlibs;
	PrepareForBuild(graph, cluster, buildlibs, false);
	
	//Walk the graph and display the list of tests
	printf("Available tests:\n");
	vector<string> tests;
	for(size_t i=0; i<graph->testsize(); i++)
	{
		UnitTestNode* test = dynamic_cast<UnitTestNode*>(graph->GetTest(i));
		if(!test)
			continue;
		tests.push_back(test->GetName());
	}
	sort(tests.begin(), tests.end());
	for(size_t i=0; i<tests.size(); i++)
		printf(" * %s\n", tests[i].c_str());
	
	PostBuildCleanup(graph, buildlibs);
}

void PrintTestOutput(string name)
{
	//Setup
	BuildGraph* graph = NULL;
	Cluster* cluster = NULL;
	vector<void*> buildlibs;
	PrepareForBuild(graph, cluster, buildlibs, false);
	
	//Find the test
	UnitTestNode* test = dynamic_cast<UnitTestNode*>(graph->GetTest(name));
	if(test == NULL)
	{
		printf("Test \"%s\" does not exist\n", name.c_str());
		return;
	}
	
	//Get the hash of the test
	string hash = test->GetHashCached();
	DebugPrintfSilent(1, "Test \"%s\" has hash \"%s\"\n", name.c_str(), hash.c_str());
	
	//See if the output exists
	string output_path = graph->GetMetaDir() + "/" + hash + "/output.txt";
	if(DoesFileExist(output_path))
	{
		printf("Test \"%s\" is up to date\n\n", name.c_str());
		
		FILE* fp = fopen(output_path.c_str(), "r");
		if(!fp)
			FatalError("Output file seems to exist, but cannot open it\n");
		char buf[1024];
		while(NULL != fgets(buf, sizeof(buf), fp))
			printf("%s", buf);
		fclose(fp);
	}
	else
		printf("Test \"%s\" is out of date\n", name.c_str());
	
	PostBuildCleanup(graph, buildlibs);
}

void RerunTest(string name)
{
	//Setup
	BuildGraph* graph = NULL;
	Cluster* cluster = NULL;
	vector<void*> buildlibs;
	PrepareForBuild(graph, cluster, buildlibs, true);
	
	//Find the test
	UnitTestNode* test = dynamic_cast<UnitTestNode*>(graph->GetTest(name));
	if(test == NULL)
	{
		printf("Test \"%s\" does not exist\n", name.c_str());
		return;
	}
	
	//Force a rebuild
	double tstart = GetTime();
	test->ForceUpdate(cluster);
	
	//Wait for the build to complete
	WaitForBuildJobsToFinish(*graph);
	double dt = GetTime() - tstart;
	
	//Print the output of the test
	DebugPrintfHeading(1, "Testing complete (in %.2f sec)\n", dt);
	PrintBuildOutput(*graph);
	string hash = test->GetHashCached();
	DebugPrintfSilent(1, "Test \"%s\" has hash \"%s\"\n", name.c_str(), hash.c_str());
	string output_path = graph->GetMetaDir() + "/" + hash + "/output.txt";
	DebugPrintfHeading(1, "Test output:\n");
	FILE* fp = fopen(output_path.c_str(), "r");
	if(!fp)
		FatalError("Failed to open output file\n");
	char buf[1024];
	while(NULL != fgets(buf, sizeof(buf), fp))
		printf("%s", buf);
	fclose(fp);
	
	//Done
	RebuildProfiling(graph);
	PostBuildCleanup(graph, buildlibs);
}

void Update()
{
	BuildGraph* graph = NULL;
	Cluster* cluster = NULL;
	vector<void*> buildlibs;
	PrepareForBuild(graph, cluster, buildlibs, true);
	
	RebuildProfiling(graph);
	PostBuildCleanup(graph, buildlibs);
}

void BuildTarget(string target)
{
	//Setup
	BuildGraph* graph = NULL;
	Cluster* cluster = NULL;
	vector<void*> buildlibs;
	PrepareForBuild(graph, cluster, buildlibs, true);
	
	//Find the test
	BuildGraphNode* node = graph->GetTarget(target);
	if(node == NULL)
	{
		printf("Node \"%s\" does not exist\n", target.c_str());
		return;
	}
	
	//Force a rebuild
	double tstart = GetTime();
	node->ForceUpdate(cluster);
	
	//Wait for the build to complete
	WaitForBuildJobsToFinish(*graph);
	double dt = GetTime() - tstart;
	
	//Print the output of the test
	DebugPrintfHeading(1, "Build complete (in %.2f sec)\n", dt);
	PrintBuildOutput(*graph);
		
	//Done
	RebuildProfiling(graph);
	PostBuildCleanup(graph, buildlibs);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Build status display

/**
	@brief Prints the output of a build operation, filtering appropriately
 */
void PrintBuildOutput(BuildGraph& graph)
{
	for(size_t i=0; i<graph.size(); i++)
	{
		BuildGraphNode* node = graph.GetNode(i);
		
		//Skip unit tests
		if(dynamic_cast<UnitTestNode*>(node) != NULL)
			continue;
		
		//print output
		vector<string> output;
		if(node->GetFilteredOutput(output))
		{
			bool printed_header = false;
			
			for(size_t j=0; j<output.size(); j++)
			{
				string line = output[j];
				
				//Remove whitespace before start of the line
				size_t wcount = 0;
				while(isspace(line[wcount]) && (wcount < line.size()))
					wcount++;
				if(wcount > 0)
					line = line.substr(wcount);
				
				//Only print the header if there's at least one non-skipped line
				if(!printed_header)
				{
					printed_header = true;
					printf("%s:\n", node->GetDescription().c_str());
				}				
				
				//Trim out absolute paths
				line = str_replace(graph.GetSourceDir(), "$src", line);
				line = str_replace(graph.GetMetaDir(), "$meta", line);

				//Split the string up into N-character blocks and print with appropriate indentation
				while(true)
				{
					size_t maxw = g_screenwidth - 4;
					size_t nend = maxw;
					if(nend > line.length())
						nend = line.length();
					string block = line.substr(0, nend);
					
					//Add ANSI escape sequences around ERROR / WARNING tags
					block = str_replace("ERROR:", "\033[31;1mERROR:\033[0m", block);
					block = str_replace("WARNING:", "\033[33;1mWARNING:\033[0m", block);
					block = str_replace("error:", "\033[31;1merror:\033[0m", block);
					block = str_replace("warning:", "\033[33;1mwarning:\033[0m", block);
					block = str_replace("Error:", "\033[31;1mError:\033[0m", block);
					block = str_replace("Timing errors:", "\033[31;1mTiming errors:\033[0m", block);
					
					printf("    %s\n", block.c_str());
					
					if(nend != maxw)
						break;
					line = line.substr(nend);
				}
			}
		}
	}
}

void PrintProgressBar(int done, int total)
{
	//TODO: get screen width from curses etc
	int progress_width = g_screenwidth - 11;	//2 for "end caps"
												//4 each for numbers
												//1 for /
	
	float frac = static_cast<float>(done) / total;
	int progress = ceil(frac * progress_width);
	
	//format string and print in one big block
	string str = "";
	for(size_t i=0; i<g_screenwidth;  i++)
		str += "\b";
	str += "[";
	for(int i=0; i<progress_width;  i++)
	{
		if(i <= progress)
			str += "*";
		else
			str += "-";
	}
	str += "]";
	
	printf("%s%4d/%-4d", str.c_str(), done, total);	
	fflush(stdout);
}

/**
	@brief Wait for all active cluster jobs to complete
 */
bool WaitForBuildJobsToFinish(BuildGraph& graph)
{
	bool done = false;
	bool fail = false;
	
	int jobs_done = 0 ;
	int jobs_total = 0;
	
	//TODO: Optimize in some way to avoid excessive polling in large projects (?)
	
	while(!done)
	{
		jobs_done = 0;
		jobs_total = 0;
		
		done = true;
		for(size_t i=0; i<graph.size(); i++)
		{
			BuildGraphNode* node = graph.GetNode(i);
			ClusterJob* job = node->GetBuildJob();
			if(job == NULL)
				continue;
				
			jobs_total ++;
				
			switch(job->GetStatus())
			{
			case ClusterJob::JOB_STATE_PENDING:
			case ClusterJob::JOB_STATE_RUNNING:
				//job is not done
				done = false;
				break;
			case ClusterJob::JOB_STATE_COMPLETE:
				//job is done, no action needed
				jobs_done ++;
				break;
				
			case ClusterJob::JOB_STATE_FAILED:
			case ClusterJob::JOB_STATE_CANCELED:
			default:
				//job failed, abort
				jobs_done ++;
				fail = true;
				break;
			}
		}
		
		//Do not show progress at high verbosity since debug messages will step on it
		if( (g_debugVerbosity < 2) && (jobs_total > 0) )
			PrintProgressBar(jobs_done, jobs_total);
		
		//wait a while before polling again
		if(!done)
		{
			//if upwards of 100 jobs pending, poll every 5 sec
			if((jobs_total - jobs_done) > 100)
				usleep(5000 * 1000);
			else
				usleep(250 * 1000);
		}
	}
	
	//Do not show progress at high verbosity since debug messages will step on it
	if( (g_debugVerbosity < 2) && (jobs_total > 0) )
	{
		PrintProgressBar(jobs_done, jobs_total);
		printf("\n");
	}
	
	//Clear build jobs so we don't add to the progress total
	for(size_t i=0; i<graph.size(); i++)
		graph.GetNode(i)->ClearBuildJob();
	
	return !fail;
}

double GetTime()
{
	timespec t;
	clock_gettime(CLOCK_REALTIME,&t);
	double d = static_cast<double>(t.tv_nsec) / 1E9f;
	d += t.tv_sec;
	return d;
}

void RebuildProfiling(BuildGraph* graph)
{
	DebugPrintfHeading(1, "Rebuilding profiling data...\n");
	
	for(auto archit = graph->ProfileDirBegin(); archit != graph->ProfileDirEnd(); archit ++)
	{
		string arch = archit->first;
		auto archset = archit->second;
				
		char infopath[1024];
		snprintf(
			infopath,
			sizeof(infopath),
			"%s/%s/coverage.info",
			graph->GetOutDir().c_str(),
			arch.c_str());
		
		//Format the command
		char scmd[1024];
		snprintf(
			scmd,
			sizeof(scmd),
			"lcov --no-external --capture --base-directory %s --output-file %s",
			graph->GetSourceDir().c_str(),
			infopath);
		string cmd = scmd;
		int ndirs = 0;
		for(auto dir : archset)
		{
			//Skip nonexistent directories (for partial builds)
			if(!DoesDirectoryExist(dir))
				continue;
				
			//Check if we have any gcda files
			vector<string> flist;
			FindFilesByExtension(dir, "gcda", flist);
			if(flist.empty())
				continue;
			
			cmd += " --directory ";
			cmd += dir;
			ndirs ++;
		}
		
		//Skip if there's no coverage data for this architecture
		if(ndirs == 0)
			continue;
		
		DebugPrintfSilent(1, "    for architecture %s (%d candidate directories, %d existent)\n",
			arch.c_str(), archset.size(), ndirs);
		
		//Run it
		double start = GetTime();
		ShellCommand(cmd + " 2> /dev/null", true);
		double dt = GetTime() - start;
		DebugPrintfSilent(1, "        Data collection complete (in %.2f ms)\n", dt*1000);
		
		//Get the name of the HTML directory
		char htmldir[1024];
		snprintf(
			htmldir,
			sizeof(htmldir),
			"%s/%s/coverage_html",
			graph->GetOutDir().c_str(),
			arch.c_str());
		MakeDirectoryRecursive(htmldir, 0644);
		
		//Generate the HTML outputs
		snprintf(
			scmd,
			sizeof(scmd),
			"genhtml %s --output-directory %s --show-details",
			infopath,
			htmldir
			);
		start = GetTime();
		ShellCommand(scmd, true);
		dt = GetTime() - start;
		DebugPrintfSilent(1, "        HTML generation complete (in %.2f ms)\n", dt*1000);
			
	}
}
